home *** CD-ROM | disk | FTP | other *** search
- ; SETUPOKI.ASM -- memory-resident printer setup utility
- ;
- ; Assemble, link, and EXE2BIN to SETUPOKI.COM. To use, just run
- ; SETUPOKI, which will install itself in memory. This should
- ; be done only once (until the system is rebooted). When invoked
- ; by the Ctrl and Right Shift keys being pressed together, it
- ; will display a menu of function keys and resulting actions.
- ; Many other keys are now accepted also, as explained below.
- ; Invalid keys will cause a beep from the computer speaker.
- ;
- ; If you are planning to modify SETUP.COM (Programming-
- ; /Utilities, Volume 5, Number 12, pp. 274-276) using DEBUG,
- ; you should add 33h bytes (the length of the Ziff-Davis
- ; copyright notice we inserted into Jeff Prosise's code) to
- ; each of the offset addresses mentioned in the text. Thus,
- ; for example, the two default color-attribute bytes (4Fh,
- ; white on red, and 70h, black on white) are actually at
- ; offsets 013F and 0140, respectively, not at 010C and 010D.
- ; The 33h additional affects all the offset addresses shown.
- ; In addition, however, note that the two bytes used to modi-
- ; fy the printer port selection are at offsets 0C77 and 0C84
- ; (not a simple addition of 33h to the offsets printed).
- ;
- ; This copy of SETUPOKI.ASM has been further modified as follows:
- ; The above adjustments to the DEBUG offsets are no longer valid.
- ; The End key in the numeric pad is now used to exit from SETUPOKI.
- ; F9 resets top-of-form with 11 inch form length. Shft-F9 now
- ; resets top-of-form also, but omits the last byte of the control
- ; sequence so that a non-standard value may be entered manually.
- ; Ctrl-F1 to Ctrl-F4 are accepted to select printers LPT1 to LPT4.
- ; Alt-F1 to Alt-F4 are accepted to swap printers LPT1 to LPT4 with
- ; printer LPT1. To avoid any confusion which might result from
- ; repeated swaps, the original ports are reset before each swap.
- ; (LPT4 is not normally valid, but might be supported by some
- ; BIOS implementations, since there are 4 entries in the table.)
- ; Valid ASCII characters (less than 128) are accepted and passed
- ; to the printer for forms setup or whatever. Some control keys
- ; are very useful; for example, ^L (FF) will skip to top-of-form.
- ; Return gives CR only; use ^J if you want a LineFeed (LF).
- ;
- interrupts segment at 0h ;interrupt table segment
- org 9h*4
- keyboard_int dw 2 dup (?) ;interrupt 9 vector
- interrupts ends
- ;
- rom_bios_data segment at 40h ;ROM BIOS data area segment
- org 8h
- printer_base dw 4 dup (?) ;line printer port addresses
- ;
- org 60h
- cursor_mode dw ? ;starting and ending cursor scan lines
- rom_bios_data ends
- ;
- rom segment at 0F000h ;ROM segment
- org 0FFFEh
- ;
- machine_id db ? ;ID byte identifies machine as PCjr or other
- rom ends
- ;
- code segment para public 'code' ;code segment
- assume cs:code
- org 100h
- ;
- begin: jmp initialize ;goto initialization routine
- ;
- db '(C) Copyright 1986, Ziff-Davis Publishing Company ', 1Ah
- ;
- column_count dw ? ;width of window in columns
- cursor_type dw ? ;cursor scan line definition
- setup_status db 0 ;indicates if printer window is already active
- display_mode dw ? ;current crt display mode
- page_no dw ? ;current displayed page
- attribute1 db 4Fh ;window attribute bytes
- attribute2 db 70h
- ;
- display_table db 2Dh,29h ;display re-enable values for modes 2 and 3
- video dw 0B800h,0B900h,0BA00h,0BB00h ;starting addresses of video
- ;memory for CGA pages 0 - 3
- ;
- mono_video dw 0B000h ;video segment address for
- ;monochrome adapter
- ;
- printer_no dw 0 ;printer no. 0 - 3
- ;
- printer_save dw 4 dup (?) ;save printer port addresses
- ;
- old_kb_int label dword
- old_keyboard_int dw 2 dup (?) ;storage for old keyboard
- ;interrupt vector
- ;
- ;----------------------------------------------------------------------------
- ;Text of the Printer Setup Menu window.
- ;After initialization, text and attribute bytes are combined and stored
- ;in the WINDOW_TEXT area, and this area is used to store the contents of
- ;the screen that underlie the window when the window is called up.
- ;----------------------------------------------------------------------------
- ;
- window_buffer label word
- buffer_text db 201,26 dup (205),187
- db 186,' SETUP PRINTER LPT1 ',186
- db 186,' OKIDATA ',230,'92/93 - IBM ',186
- db 199,26 dup (196),182
- db 186,' F1 Compressed Mode ',186
- db 186,' F2 Expanded Mode ',186
- db 186,' F3 Emphasized Mode ',186
- db 186,' F4 Double-Strike Mode ',186
- db 186,' F5 Correspondence Mode ',186
- db 186,' F6 Miniature Mode ',186
- db 186,' F7 Skip Perforation ',186
- db 186,' F8 8 Lines per Inch ',186
- db 186,' F9 Reset Top-of-Form ',186
- db 186,' F10 Reset Print Modes ',186
- db 186,' END Exit ',186
- db 199,26 dup (196),182
- db 186,' Unshifted: Toggle ON ',186
- db 186,' Shifted: Toggle OFF ',186
- db 186,' Control: Select LPTn ',186
- db 186,' Alternate: Swap LPT1,n ',186
- db 200,26 dup (205),188
- db 588 dup (?)
- ;
- ;Storage area for the combination of text and attribute bytes that
- ;form the window image.
- ;
- window_bytes label byte
- window_text dw 588 dup (?)
- ;
- ;Control code strings for all of the printer setup options.
- ;
- code_table: db 15,255,14 dup (0) ;compressed mode on
- db 27,87,1,255,12 dup (0) ;expanded mode on
- db 27,69,255,13 dup (0) ;emphasized mode on
- db 27,71,255,13 dup (0) ;double-strike mode on
- db 27,88,1,255,12 dup (0) ;correspondence on
- db 15,27,83,0,27,51,24,255,8 dup (0) ;miniature mode on
- db 27,78,12,255,12 dup (0) ;perfskip on
- db 27,48,255,13 dup (0) ;8 lines/inch
- db 27,67,0,11,255,11 dup (0) ;reset top-of-form
- db 18,27,87,0,27,70,27,72,27,88,0,27,84,27,50,255 ;reset
- ;print modes
- ;
- db 18,255,14 dup (0) ;compress off
- db 27,87,0,255,12 dup (0) ;expand off
- db 27,70,255,13 dup (0) ;emphasize off
- db 27,72,255,13 dup (0) ;double-strike off
- db 27,88,0,255,12 dup (0) ;correspondence off
- db 18,27,84,27,50,255,10 dup (0) ;miniature off
- db 27,79,255,13 dup (0) ;perfskip off
- db 27,50,255,13 dup (0) ;6 lines/inch
- db 27,67,0,255,12 dup (0) ;reset top-of-form
- ;for manual length
- ;
- ;---------------------------------------------------------------------------
- ;Execution comes here, to the main body of the program, when an interrupt 9
- ;is generated from the keyboard. Registers are saved, then the keypress is
- ;checked and compared to the key combination that activates the menu window.
- ;---------------------------------------------------------------------------
- ;
- main proc near
- sti ;enable software interrupts
- push ax ;save all registers
- push bx
- push cx
- push dx
- push si
- push di
- push ds
- push es
- push ax ;save ax for call to old routine
- in al,0A0h ;re-enable NMI on PCjr
- pop ax ;restore ax
- pushf ;simulate interrupt call to old keyboard routine
- call old_kb_int ;call old routine
- mov ah,2 ;check status of the shift keys
- int 16h
- and al,5 ;Ctrl and Rt-Shift depressed?
- cmp al,5
- je do_program ;yes, then skip exit routine
- ;
- ;Exit routine is the common point of exit for all routines in the program.
- ;
- exit: pop es
- pop ds
- pop di
- pop si
- pop dx
- pop cx
- pop bx
- pop ax
- iret
- ;
- ;Execution comes here when the proper key combination, Ctrl/Rt-Shift, is
- ;pressed. First task is to check whether or not the window is already open.
- ;
- do_program: push cs ;set es and ds to the code segment
- pop ds
- push cs
- pop es
- cmp setup_status,0 ;is the window already open?
- jne exit ;yes, then ignore request
- ;
- ;----------------------------------------------------------------------------
- ;Check current video mode. If it's mode 2, 3, or 7, then set the window
- ;status flag, store the mode number and page number, save the cursor type,
- ;and hide the cursor. If any other display mode is active instead, ignore
- ;the request and exit.
- ;----------------------------------------------------------------------------
- ;
- mov ah,15 ;get page and mode numbers
- int 10h ;al=mode, bh=page
- cmp al,2 ;is crt now in an acceptable mode?
- je prog0 ;yes, then continue
- cmp al,3
- je prog0
- cmp al,7
- je prog0
- jmp exit ;no, then ignore request
- prog0: mov setup_status,1 ;set status flag to indicate
- ;that window is active
- mov ah,0 ;save mode number
- mov display_mode,ax
- push bx
- mov bl,bh ;save page number for color displays
- mov bh,0
- mov page_no,bx
- pop bx
- mov ah,3 ;get cursor type
- int 10h
- mov cursor_type,cx ;save it
- mov ah,1 ;hide the cursor until later
- mov ch,20h
- int 10h
- ;
- ;Preparatory routines are completed. Now open the window by first saving the
- ;contents of video memory beneath the window and then writing the window text
- ;directly to memory.
- ;
- mov bx,page_no ;use bx as index into video segment
- ;address table
- cmp display_mode,7 ;manually adjust index for monochrome
- ;adapter
- jne prog1
- mov bx,4
- prog1: shl bx,1 ;multiply bx by two since
- ;table is made up of words
- mov ax,video[bx] ;read segment from table
- mov ds,ax ;ds set to video memory
- cmp display_mode,7 ;skip disable if in mode 7
- je prog2
- call video_disable ;turn display off for snow-free writing
- prog2: lea di,window_buffer ;set di to buffer area to save
- ;screen contents
- mov ch,28 ;define window dimensions and location
- mov cl,21
- mov dh,2
- mov dl,26
- call video2mem ;then transfer screen contents to buffer
- push ds ;set es to video memory
- pop es
- push cs ;reset ds to code segment
- pop ds
- ;
- prog3: lea si,window_text ;point si to window image
- mov ch,28 ;define window region
- mov cl,21
- mov dh,2
- mov dl,26
- call mem2video ;and write the window to the display
- cmp display_mode,7 ;skip enable if in mode 7
- je getkey
- call video_enable ;re-enable the video display
- ;
- ;Window is now present on the screen, so wait for a keypress.
- ;
- getkey: mov ah,0 ;get a keypress
- int 16h
- cmp al,0 ;is it an extended code?
- je extended_code ;yes, go interpret it
- cmp al,127 ;valid ASCII character?
- ja getkey1 ;no, illegal keypress
- ;
- ;A character has been entered which is to be passed thru to the printer.
- ;If the selected printer is powered off or off-line, beep instead.
- ;
- call lptnstat ;check for printer ready
- jc getkey1 ;beep if printer not ready
- mov dx,printer_no ;printer no. 0 - 3
- mov ah,0
- int 17h ;send byte to printer
- jmp getkey ;return for another keypress
- ;
- ;An extended code has been entered...check its validity and goto the
- ;appropriate routine.
- ;
- extended_code:
- cmp ah,59 ;less than F1?
- jb getkey1 ;yes, reject it
- cmp ah,68 ;F1 thru F10?
- jbe unshifted ;yes
- cmp ah,79 ;is it END key?
- je endkey ;yes, restore and quit
- cmp ah,84 ;between F10 and Shft-F1?
- jb getkey1 ;yes, reject it
- cmp ah,92 ;Shft-F1 thru Shft-F9?
- jbe shifted ;yes
- cmp ah,94 ;less than Ctrl-F1?
- jb getkey1 ;yes, reject it
- cmp ah,97 ;Ctrl-F1 thru Ctrl-F4?
- jbe control ;yes
- cmp ah,104 ;less than Alt-F1?
- jb getkey1 ;yes, reject it
- cmp ah,107 ;Alt-F1 thru Alt-F4?
- jbe alternate ;yes
- ;
- ;Getkey1 routine handles an illegal keypress by beeping and returning
- ;for another.
- ;
- getkey1: call beep ;beep and return for another keypress
- jmp getkey
- ;
- ;----------------------------------------------------------------------------
- ;Execution comes here when the END key is pressed. The window is refilled
- ;with its original contents, the cursor is restored, and control is handed
- ;back to the application program.
- ;----------------------------------------------------------------------------
- ;
- endkey: cmp display_mode,7 ;skip disable if in mode 7
- je prog4
- call video_disable ;turn off the display
- prog4: lea si,window_buffer ;point si to the buffer area
- mov ch,28 ;define the window
- mov cl,21
- mov dh,2
- mov dl,26
- call mem2video ;and write the buffer contents to the display
- cmp display_mode,7 ;skip enable if in mode 7
- je prog5
- call video_enable ;turn display back on
- prog5: mov ah,1 ;restore cursor
- mov cx,cursor_type
- int 10h
- mov setup_status,0 ;reset window status
- jmp exit ;and exit
- ;
- ;----------------------------------------------------------------------------
- ;If a legal function key was pressed, its scan code is translated here to the
- ;starting address of the string of bytes to be sent to the printer. The
- ;string is then sent to LPTn: provided it's powered on and on-line.
- ;----------------------------------------------------------------------------
- ;
- shifted: sub ah,15 ;adjustment for shifted function keys
- unshifted: sub ah,59 ;adjustment for unshifted function keys
- mov al,ah ;convert index to word in ax
- xor ah,ah
- mov cl,4 ;multiply ax by 16
- shl ax,cl
- add ax,offset code_table ;convert ax to full offset address
- mov si,ax ;and transfer it to si
- call lptnstat ;check for printer ready
- jc getkey1 ;beep if printer not ready
- mov bl,255 ;specify delimiter for call to LPRINTZ
- call lprintz ;send control code string to printer
- jmp getkey ;return for another keypress
- ;
- ;A key between Ctrl-F1 and Ctrl-F4 has been pressed. The printer being
- ;setup is changed to LPTn where Ctrl-Fn is the key that was pressed.
- ;
- control: sub ah,94 ;make relative to 0
- mov al,ah
- mov ah,0
- mov printer_no,ax ;save printer no. 0 - 3
- inc al ;make relative to 1
- or al,"0" ;change to ASCII digit
- mov window_bytes+100,al ;update window text
- ;
- cmp display_mode,7 ;skip disable if in mode 7
- je jprog3
- call video_disable ;turn display off for snow-free writing
- jprog3: jmp prog3 ;go redisplay window
- ;
- ;A key between Alt-F1 and Alt-F4 has been pressed. Printer LPT1 is
- ;swapped with printer LPTn where Alt-Fn is the key that was pressed.
- ;The printer ports are first reset to their original values.
- ;
- alternate: sub ah,104 ;make relative to 0
- mov bl,ah
- mov bh,0
- shl bx,1 ;double for word index
- ;
- push es
- mov ax,rom_bios_data ;set es to ROM BIOS data area
- mov es,ax
- assume es:rom_bios_data
- lea si,printer_save ;point si to save area
- lea di,printer_base ;and di to printer_base
- mov cx,4 ;move 4 words
- rep movsw
- ;
- mov ax,printer_base ;save LPT1 port
- mov cx,printer_base[bx] ;save LPTn port
- mov printer_base[bx],ax ;LPTn = saved LPT1
- mov printer_base,cx ;LPT1 = saved LPTn
- pop es
- assume es:nothing
- jmp getkey ;return for another keypress
- ;
- main endp ;end of main body of program
- ;
- ;---------------------------------------------------------------------------
- ;VIDEO_ENABLE and VIDEO_DISABLE routines manipulate bit 3 of port 3D8h,
- ;the CGA Mode Control Register, to temporarily turn the display on or off.
- ;Since these routines write directly to hardware, they have no effect on
- ;other video adapters.
- ;---------------------------------------------------------------------------
- ;
- video_disable proc near
- mov dx,3DAh ;read CGA status port
- disable1: in al,dx ;wait for vertical retrace to occur
- test al,8 ;is bit 3 set?
- je disable1 ;no, wait until it is
- mov dx,3D8h ;now disable the display
- mov al,25h ;by clearing bit 3 of the Mode Control Register
- out dx,al
- ret
- video_disable endp
- ;
- video_enable proc near
- mov dx,3D8h ;CGA Mode Control Register
- mov bx,display_mode ;get value to re-enable display
- sub bx,2
- mov al,display_table[bx]
- out dx,al ;and send it to the port
- ret
- video_enable endp
- ;
- ;---------------------------------------------------------------------------
- ;VIDEO2MEM routine transfers the contents of a portion of video memory
- ;to a memory buffer for storage.
- ;Entry: DS - video segment
- ; ES:DI - memory buffer
- ; DH,DL - row and column of upper left corner of window
- ; CH - width of window in columns
- ; CL - number of lines in window
- ;---------------------------------------------------------------------------
- ;
- video2mem proc near
- mov al,ch ;store number of columns
- mov ah,0
- mov column_count,ax
- mov ch,0 ;cx = number of lines
- push di ;save di
- call video_offset ;get cell address of first character
- mov si,di ;put it in si
- pop di ;restore di
- v2mem1: push si ;save si for next line
- push cx ;save line count
- mov cx,column_count ;set cx for call to WRITELN
- call writeln ;transfer one line
- pop cx ;restore saved registers
- pop si
- add si,160 ;set si for next line address
- loop v2mem1 ;loop until all lines are done
- ret
- video2mem endp
- ;
- ;---------------------------------------------------------------------------
- ;MEM2VIDEO writes a selected area of memory to the video display.
- ;Entry: DS:SI - memory buffer
- ; ES - video segment
- ; DH,DL - row and column of upper left corner of window
- ; CH - width of window in columns
- ; CL - number of lines in window
- ;---------------------------------------------------------------------------
- ;
- mem2video proc near
- mov al,ch ;save number of columns
- mov ah,0
- mov column_count,ax
- mov ch,0 ;cx = number of lines
- call video_offset ;get offset into video memory
- mem2v1: push di ;save video starting address
- push cx ;save line count
- mov cx,column_count ;set cx for call to WRITELN
- call writeln ;transfer one line
- pop cx ;restore registers
- pop di
- add di,160 ;set di for next display line
- loop mem2v1 ;loop until done
- ret
- mem2video endp
- ;
- ;---------------------------------------------------------------------------
- ;VIDEO_OFFSET calculates the offset into video memory of a character cell.
- ;Entry: DH,DL - row and column of cell (0-24,0-79)
- ;Exit: DI - offset address
- ;---------------------------------------------------------------------------
- ;
- video_offset proc near
- mov al,160
- mul dh ;row * 160
- shl dl,1 ;column * 2
- mov dh,0 ;byte to word
- add ax,dx ;(row *160)+(column*2)
- mov di,ax ;set offset in di
- ret
- video_offset endp
- ;
- ;---------------------------------------------------------------------------
- ;WRITELN subroutine copies a string of words from one memory location to
- ;another. The CGA status port is not checked for vertical retrace status
- ;before transfer.
- ;Entry: DS:SI - source
- ; ES:DI - destination
- ; CX - number of words
- ;---------------------------------------------------------------------------
- ;
- writeln proc near
- cld ;clear for string instructions
- write1: movsw ;move one word
- loop write1 ;loop until done
- ret
- writeln endp
- ;
- ;---------------------------------------------------------------------------
- ;LPRINTZ routine sends a string of bytes delimited by a user-specified byte
- ;to LPTn: thru INT 17h.
- ;Entry: DS:SI - string address
- ; BL - delimiter (0-255)
- ;---------------------------------------------------------------------------
- ;
- lprintz proc near
- cld ;for 8088 string instructions
- lprintz1: lodsb ;get one byte
- cmp al,bl ;is it the delimiter?
- je lprintz2 ;yes, then exit
- mov dx,printer_no ;printer no. 0 - 3
- mov ah,0
- int 17h ;send byte to printer
- jmp lprintz1 ;return for next byte
- lprintz2: ret
- lprintz endp
- ;
- ;---------------------------------------------------------------------------
- ;LPTNSTAT checks the current status of printer LPTn:. If it's either
- ;powered off or off-line, then an error condition is signalled upon return
- ;thru the carry flag.
- ;Exit: Carry clear - no error
- ; Carry set - error
- ;---------------------------------------------------------------------------
- ;
- lptnstat proc near
- mov dx,printer_no ;printer no. 0 - 3
- mov ah,2 ;use ROM BIOS 'get status' function
- int 17h
- test ah,8 ;test bit 3, I/O error indicator
- je stat1 ;if clear, then no error
- stc ;raise error flag
- ret
- stat1: clc ;clear error flag
- ret
- lptnstat endp
- ;
- ;---------------------------------------------------------------------------
- ;BEEP uses the 8253 timer chip to emit a short beep thru the PC's speaker.
- ;---------------------------------------------------------------------------
- ;
- beep proc near
- mov al,182 ;notify 8253 that frequency data is coming
- out 67,al
- mov al,0 ;send frequency (776.8 Hz)
- out 66,al
- mov al,6
- out 66,al
- in al,97 ;activate speaker
- or al,3
- out 97,al
- mov cx,6000h ;time delay for sound duration
- beep1: loop beep1
- in al,97 ;deactivate speaker
- and al,252
- out 97,al
- ret
- beep endp
- ;
- ;---------------------------------------------------------------------------
- ;Initialization routine sets up the window image in the WINDOW_TEXT area,
- ;resets the CURSOR_MODE word if this is a PCjr, and saves and replaces the
- ;old keyboard interrupt vector.
- ;---------------------------------------------------------------------------
- ;
- initialize proc near
- ;
- ;Initialize the window text area by combining the text data with the attribute
- ;bytes and placing the conglomeration in the WINDOW_TEXT area.
- ;
- assume ds:code,es:code
- mov ah,15 ;check the current video mode
- int 10h
- cmp al,7 ;if it's mode 7, then replace the attribute
- jne init0 ;bytes with ones appropriate for mono adapter
- mov attribute1,70h
- mov attribute2,07h
- init0: cld ;now combine the text and attribute bytes
- lea si,buffer_text ;point si to table of text
- lea di,window_bytes ;and di to storage area
- mov cx,112 ;create first four lines by combining
- mov al,attribute1 ;text with attribute1 (112 words)
- init1: movsb ;text byte
- stosb ;attribute byte
- loop init1 ;loop until all 112 words are done
- mov cx,11 ;now do the next 11 lines
- init2: push cx ;first attribute in each line is attribute1
- movsb
- stosb
- mov cx,26 ;next 26 attributes are attribute2
- mov al,attribute2
- init3: movsb
- stosb
- loop init3
- movsb
- mov al,attribute1 ;and the last in each line is attribute1
- stosb
- pop cx
- loop init2 ;loop until all 11 lines are done
- mov cx,168 ;create the last six lines just like
- init4: movsb ;the first four
- stosb
- loop init4
- ;
- ;Now save the printer_base table (port addresses) for printer swapping.
- ;
- mov ax,rom_bios_data ;set ds to ROM BIOS data area
- mov ds,ax
- assume ds:rom_bios_data
- lea si,printer_base ;point si to printer_base
- lea di,printer_save ;and di to save area
- mov cx,4 ;move 4 words
- rep movsw
- ;
- ;Check the machine ID byte in ROM and if this is a PCjr, then reset the
- ;cursor and correct the CURSOR_MODE word at 0040:0060.
- ;
- mov ax,rom ;set ds to rom
- mov ds,ax
- assume ds:rom
- cmp machine_id,0FDh ;is this a PCjr?
- jne init5 ;no, then skip this routine
- mov ax,rom_bios_data ;set ds to ROM BIOS data area
- mov ds,ax
- assume ds:rom_bios_data
- mov cursor_mode,0607h ;reset the cursor mode indicator
- mov ah,1 ;then physically reset the cursor
- mov cx,0607h
- int 10h
- ;
- ;Now save the old keyboard interrupt vector and replace it with the new one.
- ;
- init5: mov ax,interrupts ;set ds to the interrupt vector area
- mov ds,ax
- assume ds:interrupts
- mov ax,keyboard_int ;save old vector
- mov old_keyboard_int,ax
- mov ax,keyboard_int[2]
- mov old_keyboard_int[2],ax
- cli ;disable all interrupts but NMI
- mov keyboard_int,offset main ;and install new vector
- mov keyboard_int[2],cs
- sti ;re-enable interrupts
- mov dx,offset initialize ;point dx to end of resident section
- int 27h ;terminate-but-stay-resident
- initialize endp
- ;
- code ends
- end begin